#include "bayer2rgb.h"
#include "bayer2rgb-internal.h"
#include "convert-internal.hh"

#include <cstring>

template<typename INFMT, typename OUTFMT>
static bool reduce_outer1(struct image_info *info, void const *in, void *out,
			  struct image_conversion_info *conv_info)
{
	typedef struct infmt<INFMT>		infmt_t;
	typedef struct infmt<OUTFMT>		outfmt_t;
	typename infmt_t::bitfmt_t const	*in_data =
		static_cast<typename infmt_t::bitfmt_t const *>(in);
	typename outfmt_t::bitfmt_t		*out_data =
		static_cast<typename outfmt_t::bitfmt_t *>(out);

	unsigned int		width = info->w;
	unsigned int		height = info->h;
	size_t			in_stride = info->stride / sizeof *in_data;
	size_t			out_stride =
		(info->w + sizeof *out_data - 1) / sizeof *out_data;
	int const		rsft =
		static_cast<int>(infmt_t::bpp) - static_cast<int>(outfmt_t::bpp);

	if ((info->stride % sizeof *in_data) != 0) {
		set_fallback_reason(conv_info, "cc: odd input stride");
		return false;
	}

	for (unsigned int y = 0; y < height; ++y) {
		for (unsigned int x = 0; x < width; ++x) {
			unsigned int	v = infmt_t::as_uint(in_data[x]);

			if (rsft < 0) {
				v <<= -rsft;
			} else if (rsft > 0) {
				// round value
				v  += (1u << (rsft - 1));
				v >>= rsft;

				if (v > outfmt_t::max)
					v = outfmt_t::max;
			}

			out_data[x] = outfmt_t::as_bitfmt(v);
		}

		in_data += in_stride;
		out_data += out_stride;
	}

	info->stride = out_stride * sizeof *out_data;
	info->bpp = outfmt_t::bpp;

	return  true;
}

template<typename OUTFMT>
static bool reduce_outer0(struct image_info *info, void const *in, void *out,
			  struct image_conversion_info *conv_info)
{
	/* Step 2: split by input bpp */
	switch (info->bpp) {
	case 8:
		return reduce_outer1<uint8_t, OUTFMT>(info, in, out, conv_info);

	case 10:
		if (info->endian == image_info::BAYER_E_LITTLE)
			return reduce_outer1<struct bitfmt_10le, OUTFMT>(info, in, out, conv_info);
		else
			return reduce_outer1<struct bitfmt_10be, OUTFMT>(info, in, out, conv_info);

	case 12:
		if (info->endian == image_info::BAYER_E_LITTLE)
			return reduce_outer1<struct bitfmt_12le, OUTFMT>(info, in, out, conv_info);
		else
			return reduce_outer1<struct bitfmt_12be, OUTFMT>(info, in, out, conv_info);

	case 16:
		if (info->endian == image_info::BAYER_E_LITTLE)
			return reduce_outer1<struct bitfmt_16le, OUTFMT>(info, in, out, conv_info);
		else
			return reduce_outer1<struct bitfmt_16be, OUTFMT>(info, in, out, conv_info);

	default:
		set_fallback_reason(conv_info, "cc: unsupported input bpp");
		return false;
	}
}

bool bayer2rgb_reduce_bpp_cc(struct image_info *info,
			     void const *in, void *out, unsigned int out_bpp,
			     struct image_conversion_info *conv_info)
{
	bool		handled;

	if (info->bpp == out_bpp) {
		if (in != out)
			memcpy(out, in, info->stride * info->h);

		return true;
	}

	switch (out_bpp) {
	case 8:
		handled = reduce_outer0<uint8_t>(info, in, out, conv_info);
		break;

	default:
		set_fallback_reason(conv_info, "cc: unsupported output bpp");
		handled = false;
		break;
	}

	return handled;
}
